// <copyright file="YUIScriptBaseControl.cs" company="YUIAsp.Net">
// Copyright (c) 2009 All Right Reserved
// </copyright>
// <author>raul popescu</author>
// <email>dev@yuiasp.net</email>
// <date>2009-01-30</date>
// <summary>YUIAsp.Net File</summary>

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Globalization;
using System.Text;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

[assembly: WebResource("YUIAspNet.EmbeddedScripts.YUIAspNet.js", "text/javascript")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.loading.gif", "image/gif")]

namespace YUIAspNet.Common
{

    /// <summary>
    /// Base class for all YUIAsp.Net controls.
    /// Exposes basic functionality for all controls.
    /// </summary>
    [ToolboxItem(false)]
    public abstract class YUIScriptBaseControl : WebControl, IScriptControl
    {
        #region Constants
        private const string ENABLESKINSGLOBAL = "YUIAspNet.EnableEmbeddedSkins";
        private const string ENABLESSCRIPTSGLOBAL = "YUIAspNet.EnableEmbeddedScripts";
        private const string LOADSCRIPTMODEGLOBAL = "YUIAspNet.LoadScriptMode";
        private const string BASECDNPATH = "YUIAspNet.BaseCDNPath";

        /// <summary>
        /// The default theme name
        /// </summary>
        public const string DEFAULTTHEME = "DefaultSkin";

        /// <summary>
        /// The default css class for the control.
        /// </summary>
        public const string YUITHEME_CSSCLASS = "yui-skin-sam"; 
        #endregion

        bool _enableEmbeddedScripts = true;
        bool _enableEmbeddedSkins = true;
        ScriptMode _loadScriptMode = ScriptMode.Minimized;
        private ScriptManager _scriptManager;

        /// <summary>
        /// Initializes a new instance of the <see cref="YUIScriptBaseControl"/> class.
        /// </summary>
        protected YUIScriptBaseControl() : this("span")
        {
            
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="YUIScriptBaseControl"/> class.
        /// </summary>
        /// <param name="tagName">Name of the tag.</param>
        protected YUIScriptBaseControl(string tagName) : base(tagName)
        {
            InitializeControl();
        }

        private void InitializeControl()
        {
            EnableAnimation = true;
            LoadScriptMode = ScriptMode.Debug;

            ClientProperties = new Dictionary<string, ClientProperty>();
            ClientEvents = new Dictionary<string, string>();
            ClientReferences = new List<ScriptReference>();

            ControlKey = GetType().Name;

            Init += new EventHandler(YUIScriptBaseControl_Init);
            PreRender += new EventHandler(YUIScriptBaseControl_PreRender);

            this.AutoInitialize = true;

            object[] ntaList = this.GetType().GetCustomAttributes(typeof(NotThemableAttribute), true);
            bool notThemable = ntaList != null && ntaList.Length > 0;

            if (!notThemable)
                this.ThemeName = DEFAULTTHEME; 
        }

        void YUIScriptBaseControl_Init(object sender, EventArgs e)
        {
            EnsureChildControls();
        }
                
        void YUIScriptBaseControl_PreRender(object sender, EventArgs e)
        {
            //ensure existance of scriptmanager.
            if (!DesignMode)
            {
                _scriptManager = ScriptManager.GetCurrent(Page);
                if (_scriptManager != null)
                {
                    _scriptManager.RegisterScriptControl(this);
                    _scriptManager.RegisterScriptDescriptors(this);
                }
                else
                    throw new InvalidOperationException("You must have a ScriptManager on the page!");
            }

            object[] ntaList = this.GetType().GetCustomAttributes(typeof(NotThemableAttribute), true);
            bool notThemable = ntaList != null && ntaList.Length > 0;

            if (this.EnableEmbeddedSkin && !notThemable)
            {
                string currentClass = CurrentTheme2CssClass();
                
                if(this.CssClass.IndexOf(currentClass, StringComparison.OrdinalIgnoreCase) == -1)
                    this.CssClass += " " + CurrentTheme2CssClass();

                RegisterStyleSheets();
            }
        }

        /// <summary>
        /// Currents the theme to a CSS class.
        /// </summary>
        /// <returns></returns>
        protected string CurrentTheme2CssClass()
        {
            //sam is YUI's enhanced theme.
            if (ThemeName == DEFAULTTHEME)
                return YUITHEME_CSSCLASS;

            return ThemeName;
        }

        #region Virtual members

        /// <summary>
        /// Registers the style sheets for the current control.
        /// Must be overriden in the parent control to add any css file reference.
        /// </summary>
        protected abstract void RegisterStyleSheets();

        /// <summary>
        /// Register the values to send to the client control for initialization.
        /// Must be overriden in parent control to add client initialization properties and events.
        /// </summary>
        protected abstract void RegisterClientInitialization();

        /// <summary>
        /// Registers any additional scripts to include for this control.
        /// Must be overriden in parent control to add depeding scripts.
        /// </summary>
        protected abstract void RegisterScriptReferences();
        #endregion

        /// <summary>
        /// Sends the control properties and events to the client
        /// </summary>
        /// <returns></returns>
        public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            YUIControlDescriptor descriptor = new YUIControlDescriptor(TypeName ?? GetType().FullName, ClientID);
            
            if(!string.IsNullOrEmpty(OnClientBeforeInitialization))
                descriptor.AddEvent("onBeforeInitialization", OnClientBeforeInitialization);

            if(!string.IsNullOrEmpty(OnClientAfterInitialization))
                descriptor.AddEvent("onAfterInitialization", OnClientAfterInitialization);

            RegisterClientInitialization();
            AddProperty("AutoInitialize", AutoInitialize);

            foreach (KeyValuePair<string, ClientProperty> clientProperty in ClientProperties)
            {
                descriptor.AddProperty(clientProperty.Key, clientProperty.Value);
            }

            foreach (KeyValuePair<string, string> clientEvent in ClientEvents)
            {
                descriptor.AddEvent(clientEvent.Key, clientEvent.Value);
            }

            yield return descriptor; 
        }

        /// <summary>
        /// Gets a collection of <see cref="T:System.Web.UI.ScriptReference"/> objects that define script resources that the control requires.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerable"/> collection of <see cref="T:System.Web.UI.ScriptReference"/> objects.
        /// </returns>
        public IEnumerable<ScriptReference> GetScriptReferences()
        {
            //enabled scripts are disabled. User will handle client script includes.
            if (!EnableEmbeddedScripts)    
                yield break;

            //get YUI scripts on which the control relays
            foreach (ScriptReference baseRef in FindDependingScriptReferences())
                ClientReferences.Add(baseRef);

            // add base class reference
            AddScriptReference(new ScriptReference("YUIAspNet.EmbeddedScripts.YUIAspNet.js", GetType().Assembly.FullName));

            // allows the implementor to register it's custom scripts
            RegisterScriptReferences();

            foreach (ScriptReference reference in ClientReferences)
                    yield return reference;

        }

        /// <summary>
        /// Adds a new property.
        /// If the property is a complex object, it would be serialized using the ms ajax default serializer
        /// </summary>
        /// <param name="name">Property name</param>
        /// <param name="value">Property value</param>
        protected void AddProperty(string name, object value)
        {
            AddProperty(name, value, false);
        }

        /// <summary>
        /// Adds a new property.
        /// </summary>
        /// <param name="name">Client property name</param>
        /// <param name="value">Client value. If parsed is true, value should be a validJSON string</param>
        /// <param name="parsed">True if value is already serialized, false otherwise</param>
        protected void AddProperty(string name, object value, bool parsed)
        {
            ClientProperties.Add(name, new ClientProperty(value, parsed));
        }

        /// <summary>
        /// Adds a new event
        /// </summary>
        /// <param name="name">Name of the event</param>
        /// <param name="jsHandler">Javascript handler.</param>
        protected void AddEvent(string name, string jsHandler)
        {
            ClientEvents.Add(name, jsHandler);
        }

        /// <summary>
        /// Adds the script reference.
        /// </summary>
        /// <param name="reference">The reference.</param>
        protected void AddScriptReference(ScriptReference reference)
        {
            ClientReferences.Add(reference);
        }

        /// <summary>
        /// Gets the type of the script rerefence from script.
        /// </summary>
        /// <param name="yuiScriptName">Name of the yui script.</param>
        /// <returns></returns>
        internal ScriptReference GetScriptRerefenceFromScriptType(YUIScriptName yuiScriptName)
        {
            YUILibraryItem item = new YUILibraryItem(yuiScriptName);
            return item.GetScriptReference(LoadScriptMode);
        }

        /// <summary>
        /// Gets the base CDN path.
        /// </summary>
        /// <value>The base CDN path.</value>
        protected static string BaseCDNPath
        {
            get
            {
                return ConfigurationManager.AppSettings[BASECDNPATH];
            }
        }

        /// <summary>
        /// Gets the depending script references.
        /// </summary>
        /// <returns></returns>
        protected IEnumerable<ScriptReference> FindDependingScriptReferences()
        {
            const string version = "2.6.0/build/";

            //all YUI scripts...
            if (!string.IsNullOrEmpty(BaseCDNPath))
            {
                foreach (ScriptReference script in YUIScriptDependencyResolver.GetScriptDescriptors(ControlKey, LoadScriptMode, EnableAnimation))
                {
                    StringBuilder sbScripts = new StringBuilder();

                    string scriptName = new StringBuilder(script.Name)
                            .Replace(".js", "") // remove extension
                            .Replace("YUIAspNet.References.", "") // remove base path
                            .Replace(".", "/") //transform yahoo.yahoo-debug -> yahoo/yahoo-debug
                            .Replace("yahoodomevent", "yahoo-dom-event") // changed name for this resource to be able to embed it..
                            .Append(".js") // -> re-add extension
                            .ToString();

                    sbScripts.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", version, scriptName);

                    yield return new ScriptReference(sbScripts.Insert(0, BaseCDNPath).ToString());
                }
            }
            else
            {
                foreach (ScriptReference script in YUIScriptDependencyResolver.GetScriptDescriptors(ControlKey, LoadScriptMode, EnableAnimation))
                    yield return script;
            }
        }

        /// <summary>
        /// Gets or sets the client properties.
        /// </summary>
        /// <value>The client properties.</value>
        protected IDictionary<string, ClientProperty> ClientProperties { get; private set; }

        /// <summary>
        /// Gets or sets the client events.
        /// </summary>
        /// <value>The client events.</value>
        protected IDictionary<string, string> ClientEvents { get; private set; }

        /// <summary>
        /// Gets or sets the client references.
        /// </summary>
        /// <value>The client references.</value>
        protected IList<ScriptReference> ClientReferences { get; private set; }

        /// <summary>
        /// The key under we find the control's references
        /// </summary>
        protected string ControlKey { get; set; }

        /// <summary>
        /// Overrides the name of the type.
        /// If this values isn't set, we get the full type name of the current instance
        /// </summary>
        /// <value>The name of the type.</value>
        protected string TypeName { get; set; }

        #region Public properties
        /// <summary>
        /// Gets the image from skin.
        /// </summary>
        /// <param name="controlName">Name of the control.</param>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        protected string FindSkinImage(string controlName, string fileName)
        {
            //if controlName is null, image is in the theme folder root.
            if(controlName == null)
                return Page.ClientScript.GetWebResourceUrl(this.GetType(), string.Format(CultureInfo.InvariantCulture, "YUIAspNet.EmbeddedSkins.{0}.{1}", ThemeName, fileName));

            return Page.ClientScript.GetWebResourceUrl(this.GetType(), string.Format(CultureInfo.InvariantCulture,"YUIAspNet.EmbeddedSkins.{0}.{1}.{2}", ThemeName, controlName, fileName));
        }

        /// <summary>
        /// Registers an embedded stylesheet.
        /// </summary>
        /// <param name="controlName">Name of the control.</param>
        /// <param name="fileName">Name of the file.</param>
        protected void RegisterEmbeddedStylesheet(string controlName, string fileName)
        {
            string path = string.Format(CultureInfo.InvariantCulture, "YUIAspNet.EmbeddedSkins.{0}.{1}.{2}", ThemeName, controlName, fileName);

            //is the file already registered?
            string filePath = Page.ClientScript.GetWebResourceUrl(GetType(), path);
            foreach (Control ctl in Page.Header.Controls)
            {
                HtmlLink link = ctl as HtmlLink;
                if (link != null && link.Href == filePath)
                    return;
            }

            HtmlLink css = new HtmlLink();

            css.Href = Page.ClientScript.GetWebResourceUrl(GetType(), path);
            css.Attributes["rel"] = "stylesheet";
            css.Attributes["type"] = "text/css";
            css.Attributes["media"] = "all";
            Page.Header.Controls.Add(css);
        }
        
        /// <summary>
        /// Enable / disable embedded skins.
        /// Turn this feature off if you are planning to use your custom skin.
        /// </summary>
        public bool EnableEmbeddedSkin
        {
            get
            {
                bool enableEmbeddedSkinsGlobal = true;
                if (ConfigurationManager.AppSettings[ENABLESKINSGLOBAL] != null
                        && ConfigurationManager.AppSettings[ENABLESKINSGLOBAL] == "false")
                    enableEmbeddedSkinsGlobal = false;

                return _enableEmbeddedSkins && enableEmbeddedSkinsGlobal;
            }
            set { _enableEmbeddedSkins = value; }
        }

        /// <summary>
        /// Enable / disable embedded scripts.
        /// Turn this feature off if you are planning to use your custom scripts.
        /// </summary>
        public bool EnableEmbeddedScripts
        {
            get
            {
                bool enableEmbeddedScriptsGlobal = true;
                if (ConfigurationManager.AppSettings[ENABLESSCRIPTSGLOBAL] != null
                        && ConfigurationManager.AppSettings[ENABLESSCRIPTSGLOBAL] == "false")
                    enableEmbeddedScriptsGlobal = false;

                return _enableEmbeddedScripts && enableEmbeddedScriptsGlobal;
            }
            set { _enableEmbeddedScripts = value; }
        }

        /// <summary>
        /// Enable / disable animation.
        /// Actually, what this flag does is include yui animation scripts.
        /// So, in the case you want animation for a control, it will be enabled for the entire page.
        /// </summary>
        public bool EnableAnimation { get; set; }

        /// <summary>
        /// Get / set the way to load the embedded scripts.
        /// </summary>
        public ScriptMode LoadScriptMode
        {
            get
            {
                if (ConfigurationManager.AppSettings[LOADSCRIPTMODEGLOBAL] != null)
                {
                    return (ScriptMode)Enum.Parse(typeof(ScriptMode), ConfigurationManager.AppSettings[LOADSCRIPTMODEGLOBAL], true);
                }

                return _loadScriptMode;
            }
            set { _loadScriptMode = value; }
        }

        /// <summary>
        /// Gets or sets the on client before initialization javascript function.
        /// This function will execute before starting the initialization of the YUI widget.
        /// </summary>
        /// <value>The on client before initialization.</value>
        public string OnClientBeforeInitialization { get; set; }

        /// <summary>
        /// Gets or sets the on client after initialization.
        /// This function will execute after the initialization of the YUI widget.
        /// </summary>
        /// <value>The on client after initialization.</value>
        public string OnClientAfterInitialization { get; set; }

        /// <summary>
        /// Gets or sets the name of the theme.
        /// </summary>
        /// <value>The name of the theme.</value>
        public virtual string ThemeName { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this control auto initializes, or is initialized from another control.
        /// </summary>
        /// <value><c>true</c> to auto initialize; otherwise, <c>false</c>.</value>
        public bool AutoInitialize { get; set; } 

        #endregion
    }
}
